<!DOCTYPE html>
<html>
<head lang="zh-cn">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>cellar 技术文档 - 网站 title</title>
    <script>
        // 用户通过搜索引擎到文章详情页时，跳到首页并进行哈希路由
        var href = window.location.href.replace(/[^:|\/]\//,function (matchStr){
            return matchStr + 'index.html#!';
        });
        window.location.href = href.replace('/main.html', '');
    </script>
</head>
<body>
<header>
    <h1> 网站的主标题 </h1>
    <h2> 网站的副标题 </h2>
</header>
<div>
    <h1 id="header-1">cellar 技术文档</h1>
<blockquote>
<p>我觉的一个博客系统最起码要具备三个功能：写文章，展示文章，管理文章；还有三个加分点：良好的SEO，结构清晰可二次开发，免费。cellar 恰好具备这六点，让我从技术角度为你娓娓道来。</p>
</blockquote>
<h2 id="header-1-1">框架概述</h2>
<p>cellar 的定位是博客静态站点生成和管理工具，为了方便将内容生成为静态站点并且方便二次开发，cellar 的使用采取命令行的形式，除了最基本的 NodeJs 不需要安装任何辅助工具，技术栈上你只需要了解 html、css、js 就可以对本工具进行二次开发。</p>
<p>在代码层面 cellar 将功能分为三层：</p>
<ul>
<li>init-site 在 tool 下，定位为站点初始化工具，作用是打包站点资源并将站点配置自动推送到整个站点。</li>
<li>publish 也在 tool 下，定位为内容发布工具，作用是对文章进行数据处理，方便 SEO，方便列表化呈现等。</li>
<li>web 前端的所有资源文件。</li>
</ul>
<p>还有一个辅助工具 server，自开发的轻量级 web 容器，方便运行静态站点和实验研究。代码目录如下：</p>
<pre><code>${root}
    ├── server 
    └── tool   站点辅助工具
        ├── init-site
        └── publish
    └── web    站点的全部资源都在这里
</code></pre><p>他们之间的具体联系和技术细节下面分章节具体讲述，由于上面介绍的三层功能都是上层为下层提供辅助服务，理解了下层上层就非常好理解了，所以我们从 web 开始介绍。</p>
<h2 id="header-1-2">前端功能模块</h2>
<h3 id="header-1-2-1">概述</h3>
<p>先看目录结构：</p>
<pre><code>web 站点的全部资源都在这里
    ├── articles 博客的全部文章和 Demo 都在这里
    ├── dist     站点源码被工程化压缩后放在这里，供发布后的站点直接使用
    └── src      站点的功能代码
        ├── components 自定义组件，博客站点的全部功能模块都实现了组件化，也就是整站组件化
        └── dep        依赖的第三方库
</code></pre><p>cellar 生成的博客是一个单页应用，为了可以被搜索引擎自动收录我们在首页的 <code>noscript</code> 标签中生成了网站的标题和文章列表，<code>noscript</code> 标签中的内容没有样式但内容齐全，列表中的每篇文章有一个链接，链接到对应文章的详情页，文章详情页也是自动生成的，这样搜索引擎可以方便的抓取到博客的全部内容。在文章的详情页单独放了一段 js，如果用户通过搜索引擎来到博客，通过改变路径格式再回到单页应用的详情上来，代码如下：</p>
<pre><code>var href = window.location.href.replace(/[^:|\/]\//,function (matchStr){
    return matchStr + &#39;index.html#!&#39;;
});
window.location.href = href.replace(&#39;/main.html&#39;, &#39;&#39;);
</code></pre><p>js 放在 <code>noscript</code> 标签的前面，不需要等 <code>noscript</code> 中的内容加载完成就可以执行，在一定程度上保证了加载速度。虽然首页的文章列表使用后台直接生成可以进一步加快首页的渲染，但是在这个连 css 和装饰图都模块化的站点中样式很难被单独提前加载，提前展示内容意义不大体验也不是很友好，另外当前方案首页初次平均加载时间已小于1秒，继续优化弊大于利。</p>
<p>功能架构上使用 <a href="http://cn.vuejs.org/" target="_blank">Vue</a> 最为基础，<a href="http://vuejs.github.io/vue-router/zh-cn/index.html" target="_blank">Vue-router</a>作为路由，jQuery 予以辅助，自己封装 webpack 和 gulp 作为工程化打包工具。Vue 提供了很好的组件化支持，cellar整个站点共有6个组件，而且只有6个组件和一个首页实现了整站组件化。下面一一介绍这六个组件</p>
<h3 id="header-1-2-2">入口模块</h3>
<p><code>main</code> 是站点的入口。这里引入最基础的 css 样式重置库 <code>normalize.css</code> 和 整站公用样式 <code>main.css</code>，<code>cellar</code> 采用响应式布局，在移动和 PC 上都能优雅的展示。另外整个单页应用的模块路由管理也在此模块中。如果你对 Vue 不是很熟练，建议开发前去读一下它的官方 API，其中组件化开发的思想和技巧是需要你掌握的，这里有相关资料的<a href="http://cn.vuejs.org/guide/components.html" target="_blank">传送门</a>。其中文章列表模块和文章详情模块互斥，用路由处理他们的呈现逻辑。</p>
<h3 id="header-1-2-3">图标模块</h3>
<p><code>icon</code> 中是两个 CSS 文件，webpack 提供了强大的模块定义和引用功能，这两个 CSS 文件可以作为两个单独的模块，在其他模块中可以 <code>require(&#39;../icon/tag.css&#39;)</code> 这样引入样式，然后在元素上直接将 <code>icon-tag</code> 添加到 <code>class</code> 属性中就可以在页面上得到一个 tag 图标了。这两个图标都是用一个元素结合 before 和 after 伪元素使用纯 CSS 绘制。</p>
<h3 id="header-1-2-4">头模块和尾模块</h3>
<p><code>header</code> 和 <code>footer</code> 是站点的头和尾，都是非常简单的模块。头模块中预留了导航功能，现在内容简单还没有必要开启此功能。</p>
<h3 id="header-1-2-5">文章列表模块</h3>
<p><code>article-list</code>，将数据循环一下展示到页面中，数据在<code>articles/articles.json</code>(此文件由工程化辅助工具 <code>publish</code> 生成) 中，前端直接请求此文件来获取文章列表数据。还有发布时间也没做显示，欢迎贡献代码。</p>
<h3 id="header-1-2-6">文章详情模块</h3>
<p><code>article-detail</code>，文章详情模块的作用是异步获取文章的内容载体<code>main.md</code>文件，然后将文件内容解析成 html 填充到页面中展示。</p>
<p>文章主体内容载体为<a href="http://wowubuntu.com/markdown/" target="_blank">markdown</a>文档，主体内容包括标题、概述、文章内容，采用 markdown 形式让你更关注内容，格式交给解析器和样式来处理，这能保证你所有的文章在格式上的统一。如果你需要为每一篇文章定义不同的标题或段落片段的格式，我想你更需要一个富文本编辑器，请出门左转注册某博客。</p>
<p>cellar 用的 markdown 解析器是开源库 <a href="https://github.com/chjj/marked" target="_blank">marked</a>，这个是一个轻量级的库，提供了对 markdown 基本语法的解析功能，同时提供了方便的自定义解析接口。还有一个好处是这个库封转的很好前后端通用，cellar 在后端生成文章单页供搜索引擎抓取，在前端直接获取 markdown 文档然后在浏览器中解析。下面是自定义解析的示例代码：</p>
<pre><code>var renderer = new marked.Renderer();
var options = {
    renderer: renderer
};    

// 重写链接的解析规则
renderer.link = function (href, title, text) {
    var attrStr = &#39;&#39;
        + &#39; href=&quot;&#39; + href + &#39;&quot;&#39;
        + (title === null ? &#39;&#39; : &#39; title=&quot;&#39; + title + &#39;&quot;&#39;)
        + &#39; target=&quot;_blank&quot;&#39;;
    return &#39;&lt;a&#39; + attrStr + &#39;&gt;&#39; + text + &#39;&lt;/a&gt;&#39;;
};

// 输出解析后的 html，options 参数可选
marked(mdContent, options);
</code></pre><p>另外详情页的文章目录也是通过 markdown 的解析器生成的数据，当前目录只支持到二级目录。</p>
<h2 id="header-1-3">工程化辅助工具</h2>
<h3 id="header-1-3-1">网站初始化工具</h3>
<p><code>init-site</code>，此工具的职责就是初始化博客整站配置。博客第一次初始化，修改了网站的整站设置信息(如title、主标题、副标题)或者修改了功能性代码需要重新打包压缩，就需要执行下面命令启动该工具：</p>
<pre><code>node tool/init-site
</code></pre><p>此工具集成了 webpack，用于打包功能模块的各种资源，webpack的配置信息在文件 <code>webpack-config.js</code> 中，作为一个 node 模块向 <code>main.js</code> 提供配置数据。在 webpack 的打包回调函数中做了版本处理，这样打出来的包的文件名会是 <code>v1.2.js</code> 这种简约优雅的形式，并且会将版本信息(包括 哈希标识、生成时间、版本号)自动写入 <code>version-map.js</code> 中。</p>
<p>在一些功能模块和页面中有网站整站设置信息，为了减少请求和代码复杂度，直接将这些数据固化到了文件中，而这些文件通过网站初始化工具高效方便地统一管理，在 <code>config.js</code> 中有网站title、主标题、副标题的配置项，还有需要统一处理的文件的模板，这对于配置网站和二次开发提供了有力的支持。</p>
<p>模板引擎使用的是 <code>ejs</code>，在 <code>init-site</code> 中模板引擎的开始和结束标记是 <code>{{</code> 和 <code>}}</code>，用来实现整站设置信息，在内容发布工具 <code>publish</code> 中引擎的开始和结束标记是 <code>&lt;%</code> 和 <code>%&gt;</code>，通过这样的一个技巧让不同的工具来处理不同层面的信息。</p>
<p>如果你需要对前端的功能模块进行二次开发，那么调试的支持是必不可少的，可以加 <code>-d</code> 参数启动该工具，通过 <code>sources map</code> 技术来调试，命令如下：</p>
<pre><code>node tool/init-site -d
</code></pre><p>另外 <code>init-site</code> 会自动调用 <code>publish</code>，因为整站设置信息和代码更新后必然需要重新发布。</p>
<h3 id="header-1-3-2">内容发布工具</h3>
<p><code>publish</code>，它的职责是自动处理文章并将处理得到的博客必不可少的资源(包括功能代码和文章)发布到另一个文件夹中。默认文件夹是与 <code>cellar</code> 同级的 <code>my-blog</code>，可以在 <code>config.js</code> 文件中更换路径。具体上依次共做了如下六件事：</p>
<ul>
<li>获取文章列表，读取 <code>articles</code> 文件夹下的文件生成文章数组供后面程序使用。</li>
<li>初始化data.json，需要将每篇文章的标题和概述等信息提取出来放入文章列表中。</li>
<li>生成首页，通过 <code>index-template.html</code> 模板和文章列表数据的结合，生成出对搜索引擎友好的首页。</li>
<li>生成文章详情页和数据片段，为每篇文章生成一个单页供搜索引擎收录。</li>
<li>将文章列表写入 json 文件，提供文章列表的异步数据服务。</li>
<li>复制要发布的文件到博客文件夹。</li>
</ul>
<p>另外还向外提供了一个 <code>copyInitSiteFile</code> 方法，来复制只有博客初始化才需要复制的 <code>favicon.ico</code> 和 前端功能模块压缩文件，这个方法会在 <code>init-site</code> 中被调用。</p>
<h2 id="header-1-4">许可协议</h2>
<p>上面提到的所有依赖库都是 MIT 许可协议，Cellar 本身也是 MIT 协议，所以你可以“肆无忌惮”的使用和修改 Cellar 的全部代码。</p>
<h2 id="header-1-5">作者寄语</h2>
<p>这个系统是我对前端模块化和工程化的一个探索，抛开这个工具业务本身，在技术和工程层面也很值得探索，期待更多的 <code>fork</code>,<code>watch</code>,<code>star</code> and <code>push</code>.</p>

</div>
</body>
</html>